package polygon.cli.util;

import org.apache.log4j.Logger;
import polygon.cli.Layout;
import polygon.exception.ProcessRunException;
import polygon.util.FileUtil;
import polygon.util.ProcessUtil;
import polygon.util.StringUtil;
import polygon.util.ZipUtil;
import polygon.util.impl.PostParameter;
import polygon.util.impl.UnsafeFileUtil;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author MikeMirzayanov (mirzayanovmr@gmail.com)
 */
public class ProblemUtil {
    @SuppressWarnings({"ResultOfMethodCallIgnored"})
    public static File download(URL url, String login, String password) throws IOException {
        PostParameter[] postParameters = new PostParameter[2];

        postParameters[0] = new PostParameter("login", login);
        postParameters[1] = new PostParameter("password", password);

        byte[] bytes = UnsafeFileUtil.downloadFileAsByteArray(url.toString(), Integer.MAX_VALUE, postParameters);

        File dir = File.createTempFile("problem-package-", "");
        FileUtil.deleteTotaly(dir);
        dir.mkdirs();

        ZipUtil.unzip(bytes, dir);
        return dir;
    }

    public static void setExecutable(File dir, String ext) {
        File[] inner = dir.listFiles();
        for (File file : inner) {
            file.setReadable(true);
            if (file.isFile() && file.getName().endsWith(ext)) {
                file.setExecutable(true);
            }
            if (file.isDirectory()) {
                setExecutable(file, ext);
            }
        }
    }

    public static void ejudgeDeployOrUpdateProblem(File problemDir, Logger logger, File contestDir, String letter) throws IOException, InterruptedException, ProcessRunException {
        File problemXml = new File(problemDir, "problem.xml");

        Map<String, String> params = new HashMap<String, String>();
        params.put("letter", letter.toUpperCase());
        params.put("contestDir", contestDir.getName());

        if (isWindows()) {
            ProblemUtil.setExecutable(problemDir, ".bat");
            logger.info("Running doall.bat for " + letter);
            ProcessUtil.runAndReturnOutput(true, problemDir, "cmd.exe", "/C", "doall.bat");
        } else {
            ProblemUtil.setExecutable(problemDir, ".sh");
            logger.info("Running doall.sh for " + letter);
            ProcessUtil.runAndReturnOutput(true, problemDir, "bash", "doall.sh");
        }

        ejudgeAddSolution(problemXml, logger, contestDir, letter, params);
        ejudgeAddChecker(problemXml, logger, contestDir, letter, params);
        ejudgeAddStatements(problemXml, logger, contestDir, letter, params);
        ejudgeAddTests(problemXml, logger, contestDir, letter, params);
        ejudgeProcessServeCfg(problemXml, logger, contestDir, letter);

        logger.info("Problem " + letter + " processing completed");
    }

    private static void ejudgeAddStatements(File problemXml, Logger logger, File contestDir, String letter, Map<String, String> params) throws IOException {
        String statementsPath = FileUtil.extractFromXml(problemXml, "/problem/statements/statement[@language=\"russian\"]/@path", String.class);
        if (StringUtil.isEmpty(statementsPath)) {
            statementsPath = FileUtil.extractFromXml(problemXml, "/problem/statements/statement[@language=\"english\"]/@path", String.class);
        }

        File statements = new File(problemXml.getParentFile(), statementsPath);
        String layoutStatements = Layout.getProblemStatements(params);
        if (layoutStatements.isEmpty()) {
            return;
        }

        String statementsFilename = layoutStatements + FileUtil.getExt(statements);
        if (layoutStatements.endsWith("/")) {
            statementsFilename = layoutStatements + statements.getName();
        }

        File ejudgeStatements = new File(contestDir, statementsFilename);
        ejudgeStatements.getParentFile().mkdirs();
        FileUtil.copyFile(statements, ejudgeStatements);

        logger.info("Statements has been copied to " + ejudgeStatements);
    }

    private static void ejudgeProcessServeCfg(File problemXml, Logger logger, File contestDir,
                                              String letter) throws IOException {
        File serveCfg = new File(contestDir, "conf/serve.cfg");
        if (!serveCfg.isFile()) {
            logger.warn("Can't find file " + serveCfg);
            return;
        }

        List<String> keys = new ArrayList<String>();
        List<String> values = new ArrayList<String>();

        String title = FileUtil.extractFromXml(problemXml, "/problem/statements/statement[@language='russian']/@title", String.class);
        if (StringUtil.isEmpty(title)) {
            title = FileUtil.extractFromXml(problemXml, "/problem/statements/statement[@language='english']/@title", String.class);
        }
        if (StringUtil.isEmpty(title)) {
            logger.warn("Can't find problem title");
        }

        String input = FileUtil.extractFromXml(problemXml, "/problem/judging/@input-file", String.class);
        String output = FileUtil.extractFromXml(problemXml, "/problem/judging/@output-file", String.class);

        keys.add("id");
        values.add("" + (letter.toUpperCase().charAt(0) - 'A' + 1));
        keys.add("super");
        values.add("\"Generic\"");
        keys.add("short_name");
        values.add("\"" + letter + "\"");
        keys.add("long_name");
        values.add("\"" + title + "\"");

        if (input.isEmpty()) {
            keys.add("use_stdin");
            values.add("1");
        } else {
            keys.add("use_stdin");
            values.add("0");
            keys.add("input_file");
            values.add("\"" + input + "\"");
        }

        if (output.isEmpty()) {
            keys.add("use_stdout");
            values.add("1");
        } else {
            keys.add("use_stdout");
            values.add("0");
            keys.add("output_file");
            values.add("\"" + output + "\"");
        }

        int timeLimit = FileUtil.extractFromXml(problemXml, "/problem/judging/testset[@name='tests']/time-limit", Integer.class);
        timeLimit = timeLimit / 1000 + (timeLimit % 1000 != 0 ? 1 : 0);
        keys.add("time_limit");
        values.add("" + timeLimit);

        int memoryLimit = FileUtil.extractFromXml(problemXml, "/problem/judging/testset[@name='tests']/memory-limit", Integer.class);
        keys.add("max_vm_size");
        values.add(memoryLimit / 1024 / 1024 + "M");
        keys.add("max_stack_size");
        values.add(memoryLimit / 1024 / 1024 + "M");

        ServeFileUtil.update(serveCfg, keys, values, "[problem]");
    }

    private static void ejudgeAddTests(File problemXml, Logger logger, File contestDir, String letter, Map<String, String> params) throws IOException {
        File testsDir = new File(contestDir, Layout.getTests(params));
        FileUtil.deleteTotaly(testsDir);
        testsDir.mkdirs();

        String inputPattern = FileUtil.extractFromXml(problemXml,
                "/problem/judging/testset[@name='tests']/input-path-pattern", String.class);

        String answerPattern = FileUtil.extractFromXml(problemXml,
                "/problem/judging/testset[@name='tests']/answer-path-pattern", String.class);

        int i;
        for (i = 1; ; i++) {
            File input = new File(problemXml.getParentFile(), String.format(inputPattern, i));
            File answer = new File(problemXml.getParentFile(), String.format(answerPattern, i));

            boolean was = false;
            try {
                Thread.sleep(i == 1 ? 500 : 10);
            } catch (InterruptedException e) {
                // No operations.
            }

            if (input.isFile()) {
                File ejudgeInput = new File(testsDir, String.format("%02d", i));
                FileUtil.copyFile(input, ejudgeInput);
                logger.info("Test " + i + " has been copied to " + ejudgeInput);

                was = true;
            }

            if (answer.isFile()) {
                File ejudgeAnswer = new File(testsDir, String.format("%02d.a", i));
                FileUtil.copyFile(answer, ejudgeAnswer);
                logger.info("Answer " + i + " has been copied to " + ejudgeAnswer);
            }

            if (!was) {
                break;
            }
        }
        i--;

        if (!isWindows()) {
//            try {
//                InvocationUtil.run(testsDir, "find . -type f -exec sed -i 's/.$//' {}");
//            } catch (InterruptedException e) {
//                // No operations.
//            }
            logger.info("The files are in WINDOWS encoding, please make sure that EJUDGE will convert them");
        }

        logger.info("There are " + i + " tests have been processed");
    }

    private static void ejudgeAddSolution(File problemXml, Logger logger, File contestDir, String letter, Map<String, String> params) throws IOException {
        String solutionPath = FileUtil.extractFromXml(problemXml, "/problem/assets/solutions/solution[@tag=\"main\"]/source/@path", String.class);

        File solution = new File(problemXml.getParentFile(), solutionPath);

        String layoutSolution = Layout.getSolution(params);
        if (layoutSolution.isEmpty()) {
            return;
        }

        String solutionFilename = layoutSolution + FileUtil.getExt(solution);
        if (layoutSolution.endsWith("/")) {
            solutionFilename = layoutSolution + solution.getName();
        }

        File ejudgeSolution = new File(contestDir, solutionFilename);
        ejudgeSolution.getParentFile().mkdirs();
        FileUtil.copyFile(solution, ejudgeSolution);

        logger.info("Solution has been copied to " + ejudgeSolution);
    }

    private static void ejudgeAddChecker(File problemXml, Logger logger, File contestDir, String letter, Map<String, String> params) throws IOException {
        String checkerPath = FileUtil.extractFromXml(problemXml, "/problem/assets/checker/source/@path", String.class);

        File checker = new File(problemXml.getParentFile(), checkerPath);
        String checkerFilename = Layout.getChecker(params) + FileUtil.getExt(checker);

        File ejudgeChecker = new File(contestDir, checkerFilename);
        ejudgeChecker.getParentFile().mkdirs();

        if (FileUtil.getExt(checker).equalsIgnoreCase(".cpp")
                || FileUtil.getExt(checker).equalsIgnoreCase(".cxx")) {
            byte[] bytes = FileUtil.getBytes(checker);
            byte[] prefix = getCheckerPreprocessPrefix().getBytes();
            byte[] postprocessedBytes = new byte[prefix.length + bytes.length];
            System.arraycopy(prefix, 0, postprocessedBytes, 0, prefix.length);
            System.arraycopy(bytes, 0, postprocessedBytes, prefix.length, bytes.length);
            FileUtil.writeFile(ejudgeChecker, postprocessedBytes);

            File testlibFile = new File(problemXml.getParentFile(), "files/testlib.h");
            if (testlibFile.isFile()) {
                File ejudgeTestlib = new File(ejudgeChecker.getParentFile(), testlibFile.getName());
                FileUtil.copyFile(testlibFile, ejudgeTestlib);
            }
        } else {
            FileUtil.copyFile(checker, ejudgeChecker);
        }

        logger.info("Checker has been copied to " + ejudgeChecker);
    }

    private static String getCheckerPreprocessPrefix() {
        return isWindows() ?
                "/* Autogenerated code, forces testlib to return exit codes for EJUDGE. */\r\n#define EJUDGE\r\n\r\n" :
                "/* Autogenerated code, forces testlib to return exit codes for EJUDGE. */\n#define EJUDGE\n\n";
    }

    private static boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("windows");
    }
}
